package org.witsoft.demo.graphql.extend.directive;

import graphql.GraphqlErrorException;
import graphql.execution.DataFetcherResult;
import graphql.kickstart.servlet.context.GraphQLServletContext;
import graphql.schema.*;
import graphql.schema.idl.SchemaDirectiveWiring;
import graphql.schema.idl.SchemaDirectiveWiringEnvironment;
import lombok.RequiredArgsConstructor;
import org.witsoft.demo.graphql.extend.execution.CustomerExecutionStrategy;
import org.witsoft.demo.graphql.service.AuthenticationService;

import java.util.*;

@RequiredArgsConstructor
public class AuthenticatedDirectiveWiring implements SchemaDirectiveWiring {

    private final AuthenticationService authenticationService;

    @Override
    public GraphQLFieldDefinition onField(SchemaDirectiveWiringEnvironment<GraphQLFieldDefinition> env) {
        GraphQLArgument rolesArg = env.getDirective().getArgument("roles");
        GraphQLArgument errorArg = env.getDirective().getArgument("error");
        List<String> roles = rolesArg == null ? Collections.emptyList() : (List<String>)rolesArg.getValue();
        boolean isError = errorArg == null ? false : (boolean)errorArg.getValue();
        DataFetcher originalFetcher = env.getFieldDataFetcher();
        DataFetcher newDataFetcher = (environment) -> {
            GraphQLServletContext context = environment.getContext();
            String authorization = context.getHttpServletRequest().getHeader("Authorization");
            Set<String> authorizes = null;
            if(authorization != null){
                authorizes = authenticationService.getAuthorizes(authorization);
            }
            if(authorizes == null){
                return buildErrorResult(environment, isError, 401, "未进行认证授权");
            }else{
                for (String role : roles) {
                    if(authorizes.contains(role)){
                        return originalFetcher.get(environment);
                    }
                }
                return buildErrorResult(environment, isError, 403, "无权限访问");
            }
        };
        GraphQLFieldDefinition field = env.getElement();
        GraphQLFieldsContainer parentType = env.getFieldsContainer();
        env.getCodeRegistry().dataFetcher(parentType, field, newDataFetcher);
        return field;
    }


    private Object buildErrorResult(DataFetchingEnvironment env, boolean isError, int code, String message){
        if(isError) {
            Map<String, Object> extensions = new HashMap<>();
            extensions.put("code", code);
            DataFetcherResult.Builder<Object> builder = DataFetcherResult.newResult();
            builder.error(GraphqlErrorException.newErrorException()
                    .message((message))
                    .extensions(extensions)
                    .build());
            return builder.build();
        }else{
            String fieldName = env.getField().getName();
            GraphQLOutputType fieldType = env.getFieldDefinition().getType();
            CustomerExecutionStrategy.REMOVE_FIELDS_HOLDER.get().add(fieldName);
            String fieldTypeName = fieldType.toString();
            if(fieldTypeName.contains("!")){
                if(fieldTypeName.equals("String!")){
                    return "";
                }else if(fieldTypeName.equals("Int!")){
                    return 0;
                }else if(fieldTypeName.equals("Boolean!")){
                    return false;
                }else{
                    throw new IllegalStateException(String.format("type can'nt null for field %s", fieldName));
                }
            }else{
                return null;
            }
        }
    }
}
